package com.letfox.common.util.http;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.SSLContext;

import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.NoHttpResponseException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Stopwatch;
import com.google.common.base.Strings;
import com.letfox.common.enums.CharsetEnum;
import com.letfox.common.exception.HTTPException;

/**
 * 远程操作工具类.
 */
public final class HTTPRemoteUtil {

    public static final String CHARSET = "UTF-8";

    private static final Logger logger = LoggerFactory.getLogger(HTTPRemoteUtil.class);

    private static final PoolingHttpClientConnectionManager CM = new PoolingHttpClientConnectionManager(registry());

    static {
        initConnectionManager(CM);
    }

    private static final CloseableHttpClient HTTPCLIENT = HttpClients.custom()
            .setConnectionManager(CM).setDefaultRequestConfig(requestConfig())
            .build();

    private HTTPRemoteUtil() {
    }

    private static CloseableHttpClient newHttpClient() {
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry());
        cm.setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(5000).build());
        return HttpClients.custom().setConnectionManager(cm).setDefaultRequestConfig(requestConfig()).build();
    }

    private static CloseableHttpResponse doExecuteWithRetry(
            final HttpUriRequest request, boolean retry) throws Exception {
        if (!retry) {
            return HTTPCLIENT.execute(request);
        }
        Retryer retryer = new RetryerBuilder()
                .retryTimes(2)
                .retryInterval(15, TimeUnit.MILLISECONDS)
                .retryIfExceptionOfType(new Class[] { SocketException.class, NoHttpResponseException.class, SocketTimeoutException.class 
                }).build();
        return retryer.callWithRetry(new Callable<CloseableHttpResponse>() {
            @Override
            public CloseableHttpResponse call() throws Exception {
                return HTTPCLIENT.execute(request);
            }
        });
    }

    private static ResponseResult requestAndResponse(HttpUriRequest request, boolean retry) {
        HttpEntity entity = null;
        CloseableHttpResponse response = null;
        CloseableHttpClient httpClient = null;
        try {
            httpClient = newHttpClient();
//        	httpClient = HttpClients.createDefault();
            response = httpClient.execute(request);
            int status = response.getStatusLine().getStatusCode();
            if (status == HttpStatus.SC_OK) {
                entity = response.getEntity();
                if (entity != null) {
                    return new ResponseResult(true, status, EntityUtils.toString(entity, CHARSET));
                }
                logger.error("remote call abnormal, entity is null, url: {}", request.getURI());
            }
            logger.error("remote call error, url: {}, status: {}", request.getURI(), status);
            return new ResponseResult(false, status, "");
        } catch (Exception e) {
            logger.error("HTTP Exception, url: " + request.getURI(), e);
            return new ResponseResult(false, -1, "");
        } finally {
            closeResponse(response);
            EntityUtils.consumeQuietly(entity);
            closeHttpClient(httpClient);
        }
    }

    private static ResponseResult requestAndResponse(boolean retry,
            HttpUriRequest request) {
        HttpEntity entity = null;
        CloseableHttpResponse response = null;
        try {
            response = doExecuteWithRetry(request, retry);
            int status = response.getStatusLine().getStatusCode();
            if (status == HttpStatus.SC_OK) {
                entity = response.getEntity();
                if (entity != null) {
                    return new ResponseResult(true, status, EntityUtils.toString(entity, CHARSET));
                }
                logger.error("remote call abnormal, entity is null, url: {}", request.getURI());
            }
            logger.error("remote call error, url: {}, status: {}", request.getURI(), status);
            return new ResponseResult(false, status, "");
        } catch (Exception e) {
            logger.error("HTTP Exception, url: " + request.getURI(), e);
            return new ResponseResult(false, -1, "");
        } finally {
            closeResponse(response);
            EntityUtils.consumeQuietly(entity);
        }
    }

    private static void closeResponse(CloseableHttpResponse response) {
        if (response == null) {
            return;
        }
        try {
            response.close();
        } catch (IOException e) {
            logger.error("Failed to close HTTP response.", e);
        }
    }

    private static void closeHttpClient(CloseableHttpClient httpClient) {
        if (httpClient == null) {
            return;
        }
        try {
            httpClient.close();
        } catch (Exception e) {
            logger.error("close http client error", e);
        }
    }

    public static String post(boolean retry, String url,
            Map<String, String> params) {
        return post(url, params, false, retry);
    }

    public static String post(final String url, final Map<String, String> params) {
        return post(url, params, false);
    }

    public static String post(String url, Map<String, String> params, RequestConfig requestConfig) {
        Stopwatch stopwatch = Stopwatch.createStarted();

        HttpPost httppost = new HttpPost(url);
        if (requestConfig != null) {
            httppost.setConfig(requestConfig);
        }
        setParam(httppost, params);

        logger.info("remote call: {}, params: {}", url, params);
        ResponseResult result = requestAndResponse(false, httppost);
        logger.info("response time: {}, status: {}, result: {}",
                timing(stopwatch.stop()), result.getCode(), result.getResult());

        if (result.isSuccess()) {
            return result.getResult();
        }
        throw new HTTPException(HTTPStatusEnum.V0006);
    }

    private static String post(final String url, final Map<String, String> params, boolean filterLog, boolean retry) {
        Stopwatch stopwatch = Stopwatch.createStarted();

        HttpPost httppost = new HttpPost(url);
        setParam(httppost, params);

        logger.info("remote call: {}, params: {}", url, filterLog ? "***" : params);
        ResponseResult result = requestAndResponse(httppost, retry);
        logger.info("response time: {}, status: {}, result: {}", timing(stopwatch.stop()), result.getCode(), filterLog ? "***" : result.getResult());

        if (result.isSuccess()) {
            return result.getResult();
        }

        throw new HTTPException(HTTPStatusEnum.V0006);
    }

    public static String post(final String url,
            final Map<String, String> params, boolean filterLog) {
        return post(url, params, filterLog, false);
    }

    public static String postJson(String url, String jsonParam) {
        return postJson(url, jsonParam, null);
    }
    
    public static String requestGet(String url, final Map<String, String> params) {
        return requestGet(url, params, false);
    }

    /**
     * GET请求方式
     * @param url
     * @param params
     * @param filterLog
     * @return
     */
    public static String requestGet(String url, final Map<String, String> params, boolean filterLog) {
        Stopwatch stopwatch = Stopwatch.createStarted();

        String newUrl = setParam(url, params);
        HttpGet httpget = new HttpGet(newUrl);

        logger.info("remote call: {}, params: {}", url, filterLog ? "***" : params);
        
        ResponseResult result = requestAndResponse(httpget, false);
        logger.info("response time: {}, status: {}, result: {}", timing(stopwatch.stop()), result.getCode(), filterLog ? "***" : result.getResult());

        if (result.isSuccess()) {
            return result.getResult();
        }

        throw new HTTPException(HTTPStatusEnum.V0006);
    }
    
    /**
     * POST请求方式
     * @return
     */
    public static String requestPost(String url,String params){
    	String jsonCode = null;
    	String charset = Charset.forName("UTF-8").toString();
    	try {
    		jsonCode = URLEncoder.encode(params, charset);
    		if (jsonCode == null) {
                throw new HTTPException(HTTPStatusEnum.V0006);
            }
        }catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            throw new HTTPException(HTTPStatusEnum.V0006);
        }
    	
    	HttpPost post = new HttpPost(url);
    	
    	//请求超时
    	RequestConfig requestConfig = RequestConfig.custom()
	    .setSocketTimeout(30000)
	    .setConnectTimeout(30000)
	    .setConnectionRequestTimeout(30000).build();
    	post.setConfig(requestConfig);
    	
    	//消息实体
    	StringEntity entity = new StringEntity(params, CHARSET);
    	entity.setContentEncoding(charset);
    	entity.setContentType("application/json");
    	post.setEntity(entity);
    	
    	ResponseResult result = requestAndResponse(post, false);
    	
    	 if (result.isSuccess()) {
             return result.getResult();
         }
    	 throw new HTTPException(HTTPStatusEnum.V0006);
    }
    
    // 构建唯一会话Id
    private static String getSessionId(){
    	UUID uuid = UUID.randomUUID();
    	String str = uuid.toString();
    	 return str.substring(0, 8) + str.substring(9, 13) + str.substring(14, 18) + str.substring(19, 23) + str.substring(24);
    }

    public static String postJson(String url, String jsonParam, int timeout) {
        Stopwatch stopwatch = Stopwatch.createStarted();
        String jsonEnCode = null;
        try {
        	
            jsonEnCode = URLEncoder.encode(jsonParam, CharsetEnum.UTF_8.value);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            throw new HTTPException(HTTPStatusEnum.V0006);
        }

        if (jsonEnCode == null) {
            throw new HTTPException(HTTPStatusEnum.V0006);

        }

        HttpPost httppost = new HttpPost(url);
        setTimeout(httppost, timeout);
        StringEntity entity = new StringEntity(jsonEnCode, CHARSET);
        entity.setContentType("application/json");
        httppost.setEntity(entity);

        logger.info("remote call: {}, params: {}", url, jsonParam);

        logger.info("remote call: {}, urlEncodeparams: {}", url, jsonEnCode);
        ResponseResult result = requestAndResponse(httppost, false);
        logger.info("response time: {}, status: {}, result: {}", timing(stopwatch.stop()), result.getCode(), result.getResult());

        if (result.isSuccess()) {
            return result.getResult();
        }

        throw new HTTPException(HTTPStatusEnum.V0006);
    }

    public static String postJson(String url, Map<String, Object> params) {
        return postJson(url, JSONObject.toJSONString(params), null);
    }

    public static String postJson(boolean retry, String url,
            Map<String, Object> params) {
        Stopwatch stopwatch = Stopwatch.createStarted();
        String jsonParam = JSONObject.toJSONString(params);
        HttpPost httppost = new HttpPost(url);
        StringEntity s = new StringEntity(jsonParam, CHARSET);
        s.setContentType("application/json");
        httppost.setEntity(s);

        logger.info("remote call: {}, params: {}", url, jsonParam);
        ResponseResult result = requestAndResponse(retry, httppost);
        logger.info("response time: {}, status: {}, result: {}",
                timing(stopwatch.stop()), result.getCode(), result.getResult());

        if (result.isSuccess()) {
            return result.getResult();
        }

        throw new HTTPException(HTTPStatusEnum.V0006);
    }

    public static String postJson(boolean retry, String url, String json) {
        Stopwatch stopwatch = Stopwatch.createStarted();
        HttpPost httppost = new HttpPost(url);
        StringEntity s = new StringEntity(json, CHARSET);
        s.setContentType("application/json");
        httppost.setEntity(s);

        logger.info("remote call: {}, params: {}", url, json);
        ResponseResult result = requestAndResponse(retry, httppost);
        logger.info("response time: {}, status: {}, result: {}", timing(stopwatch.stop()), result.getCode(), result.getResult());

        if (result.isSuccess()) {
            return result.getResult();
        }

        throw new HTTPException(HTTPStatusEnum.V0006);
    }

    public static String postJson(String url, String jsonParam,
            Map<String, String> headParams) {
        return postJson(url, jsonParam, headParams, false);
    }

    public static String postJson(String url, String jsonParam,
            Map<String, String> headParams, boolean filterLog) {
        Stopwatch stopwatch = Stopwatch.createStarted();

        String jsonEnCode = null;
        try {
            jsonEnCode = URLEncoder.encode(jsonParam, CharsetEnum.UTF_8.value);

        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            throw new HTTPException(HTTPStatusEnum.V0006);
        }

        if (jsonEnCode == null) {
            throw new HTTPException(HTTPStatusEnum.V0006);

        }

        HttpPost httppost = new HttpPost(url);
        addingHeaders(httppost, headParams);
        StringEntity stringEntity = new StringEntity(jsonEnCode, CHARSET);
        stringEntity.setContentType("application/json");
        httppost.setEntity(stringEntity);

        logger.info("remote call: {}, params: {}", url, filterLog ? "***" : jsonParam);
        logger.info("remote call: {}, encodeparams: {}", url, filterLog ? "***" : jsonEnCode);
        ResponseResult result = requestAndResponse(httppost, false);
        logger.info("response time: {}, status: {}, result: {}", timing(stopwatch.stop()), result.getCode(), filterLog ? "***" : result.getResult());

        if (result.isSuccess()) {
            return result.getResult();
        }

        throw new HTTPException(HTTPStatusEnum.V0006);
    }

    public static String postJsonToEcif(String url, String jsonParam,
            Map<String, String> headParams) {
        return postJsonToEcif(url, jsonParam, headParams, false);
    }

    public static String postJsonToEcif(String url, String jsonParam,
            Map<String, String> headParams, boolean filterLog) {
        Stopwatch stopwatch = Stopwatch.createStarted();

        HttpPost httppost = new HttpPost(url);
        addingHeaders(httppost, headParams);
        StringEntity s = new StringEntity(jsonParam, CHARSET);
        s.setContentType("application/json");
        httppost.setEntity(s);

        logger.info("remote call: {}, params: {}", url, filterLog ? "***" : jsonParam);
        // logger.info("remote call: {}, encodeparams: {}", url, filterLog ?
        // "***" : jsonEnCode);
        ResponseResult result = requestAndResponse(httppost, false);
        logger.info("response time: {}, status: {}, result: {}",
                timing(stopwatch.stop()), result.getCode(), filterLog ? "***" : result.getResult());

        if (result.isSuccess()) {
            return result.getResult();
        }

        throw new HTTPException(HTTPStatusEnum.V0006);
    }

    private static void setTimeout(HttpRequestBase httpRequest, int timeout) {
        // 请求http时，设置超时.
        RequestConfig requestConfig = RequestConfig.custom()
                .setSocketTimeout(timeout).setConnectTimeout(timeout)
                .setConnectionRequestTimeout(timeout).build();
        httpRequest.setConfig(requestConfig);
    }

    private static void setParam(final HttpPost httppost,
            final Map<String, String> params) {
        if (params == null || params.size() == 0) {
            return;
        }
        List<NameValuePair> formParams = new ArrayList<NameValuePair>();
        for (Map.Entry<String, String> entrySet : params.entrySet()) {
            if (StringUtils.isBlank(entrySet.getValue())) {
                continue;
            }
            formParams.add(new BasicNameValuePair(entrySet.getKey(), entrySet
                    .getValue()));
        }
        try {
            httppost.setEntity(new UrlEncodedFormEntity(formParams, CHARSET));
        } catch (IOException e) {
            logger.error("setParam error: ", e);
        }
    }

    private static String setParam(final String url,
            final Map<String, String> params) {
        if (url == null || params == null || params.isEmpty()) {
            return Strings.nullToEmpty(url);
        }

        StringBuilder stbURL = new StringBuilder(url);
        stbURL.append(url.contains("?") ? "&" : "?");

        for (Map.Entry<String, String> entrySet : params.entrySet()) {
            if (StringUtils.isBlank(entrySet.getValue())) {
                continue;
            }
            try {
                stbURL.append(entrySet.getKey())
                        .append("=")
                        .append(URLEncoder.encode(entrySet.getValue(), CHARSET))
                        .append("&");
            } catch (UnsupportedEncodingException e) {
                logger.error("Error when prepared for remote http params", e);
            }
        }
        // 删除末尾多余的 & 或 ?
        stbURL.delete(stbURL.length() - 1, stbURL.length());
        return stbURL.toString();
    }

    private static void addingHeaders(HttpPost post,
            Map<String, String> headParams) {
        if (headParams == null || headParams.isEmpty()) {
            return;
        }
        Iterator<String> iter = headParams.keySet().iterator();
        while (iter.hasNext()) {
            String key = iter.next();
            post.addHeader(key, headParams.get(key));
        }
    }

    private static long timing(Stopwatch sw) {
        return sw.elapsed(TimeUnit.MILLISECONDS);
    }

    private static void initConnectionManager(
            PoolingHttpClientConnectionManager cm) {
        cm.setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(2000)
                .build());
        cm.setDefaultMaxPerRoute(100);
        cm.setMaxTotal(500);
    }

    /**
     *  分配的 socket 的 soTimeout, 后续处理过程使用这个超时时间
     *  socket 建立网络连接, 超时时间
     *  从连接池获取连接, 最长的等待时间
     * @return
     */
    private static RequestConfig requestConfig() {
        return RequestConfig.custom().setStaleConnectionCheckEnabled(true)
                .setSocketTimeout(15000)
                .setConnectTimeout(10000)
                .setConnectionRequestTimeout(5000).build();
    }

    private static SSLContext sslContext() {
        try {
            return SSLContexts.custom().loadTrustMaterial(null, new TrustAllStrategy() {}).build();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static Registry<ConnectionSocketFactory> registry() {
        return RegistryBuilder.<ConnectionSocketFactory> create()
                .register("https", new SSLConnectionSocketFactory(sslContext(),new NopHostnameVerifier()))
                .register("http", PlainConnectionSocketFactory.getSocketFactory()) // thread-safe
                .build();
    }
}
